package app.view.component
{
	import app.model.flare.vos.StateVO;
	
	import flare.display.DirtySprite;
	import flare.display.TextSprite;
	import flare.query.methods.eq;
	import flare.vis.Visualization;
	import flare.vis.controls.ClickControl;
	import flare.vis.controls.HoverControl;
	import flare.vis.controls.IControl;
	import flare.vis.data.Data;
	import flare.vis.data.EdgeSprite;
	import flare.vis.data.NodeSprite;
	import flare.vis.data.render.ArrowType;
	import flare.vis.events.SelectionEvent;
	import flare.vis.events.VisualizationEvent;
	import flare.vis.operator.label.RadialLabeler;
	import flare.vis.operator.layout.BundledEdgeRouter;
	import flare.vis.operator.layout.CircleLayout;
	
	import flash.events.KeyboardEvent;
	import flash.geom.Rectangle;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
	
	import mx.containers.Canvas;
	import mx.events.FlexEvent;
	import mx.events.ResizeEvent;
	
	
	public class FlareViz extends Canvas
	{
		/** We will be rotating text, so we embed the font. */
		[Embed(source="fonts/verdana.ttf", fontName="Verdana")]
		private static var _font:Class;
		[Embed(source="fonts/verdanab.ttf", fontName="Verdana", fontWeight="bold")]
		private static var _fontB:Class;
			
		private var _vis:Visualization;
		private var _nodeHoverControls:IControl;
		private var _nodeClickControls:IControl;
		private var _edgeHoverControls:IControl;
		private var _edgeClickControls:IControl;
		
		private var inEdgeColour:uint = 0xff009900;
		private var outEdgeColour:uint = 0xffff0000;
		private var selectedEdgeColour:uint = 0xffff0000;
		private var rolloverEdgeColour:uint = 0xffff00ff;
		private var selectedNodeColour:uint = 0xffff0000;
		
		private var selectedEdgeAlpha:Number = 0.8;
		private var defaultEdgeAlpha:Number = 0.06;
		private var selectedEdgeThickness:Number = 4;
		private var defaultEdgeThickness:Number = 0.1;
		
		private var rollOverNodeColour:uint = 0xff0000cc;
		private var defautEdgeColour:uint = 0xff0055cc;
		
		private var _isActive:Boolean;
		private var _interactive:Boolean;
		private var _bounds:Rectangle;
		
		private var _fmt:TextFormat = new TextFormat("Verdana", 20);
		private var _focusNode:NodeSprite;
		private var _focusEdge:EdgeSprite;
		private var _selectedNode:NodeSprite;
		private var _selectedEdge:EdgeSprite;
		
		public function FlareViz():void
		{
			init();
  		}
  		
  		private function init():void{
  			addEventListener(ResizeEvent.RESIZE, onResize);
  			addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
  			
  		}
  		
  		private function onResize(event:ResizeEvent=null):void{
			resize( new Rectangle( 10, 10, width - 20, height - 20 ) );
		}
		
		private function onCreationComplete(event:FlexEvent):void{
			removeEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
			onResize();
		}
	
  		
  		public function flush():void{
  			_isActive = false;
  			removeViz();	
  		}
  		
  		private function removeViz():void{
  			if(_vis && contains(_vis)){
  				rawChildren.removeChild(_vis);
  				_vis.removeEventListener(VisualizationEvent.UPDATE, onUpdate);
  				_vis = null;
  			}
  		}
  		
  		private function onUpdate(event:VisualizationEvent):void{
  			dispatchEvent(event);
  		}
  		
  		public function get currentNode():NodeSprite{
  			return _focusNode;
  		}
  		public function get currentEdge():EdgeSprite{
  			return _focusEdge;
  		}
  		
  		public function get selectedNode():NodeSprite{
  			return _selectedNode;
  		}
  		public function set selectedNode(node:NodeSprite):void{
  			if(_selectedNode)
  				deselectNode(_selectedNode, NodeSprite.OUT_LINKS);
  			_selectedNode = node;
  			if(_selectedNode)
  				selectNode(_selectedNode, NodeSprite.OUT_LINKS);
  			
  		}
  		
  		public function set selectedState(state:StateVO):void{
  			_vis.data.nodes.visit	(
	  									function(node:NodeSprite):Boolean {
	  										if(node.data == state) {
	  											selectedNode = node;
	  											return true;
	  										}
	  										return false;
	  									}
  									)
  				
  		}
  		
  		public function deselect():void{
  			selectedNode = null;
  			forceRedraw();
  			setFocus();
  		}
  		
  		public function set dataProvider( d:Data ):void{
  			_isActive = true;
  			removeViz();
  			visualize( d );
  			interactive  = true;
  		}
  		
  		public function update():void{
  			_vis.update();
  			_selectedNode = null;
  			_selectedEdge = null
  			_focusNode = null;
  			_focusEdge = null;
  						
  		}
  		
  		public function get interactive():Boolean{
  			return _interactive;
  		}
  		
  		public function set interactive(val:Boolean):void{
  			_interactive = val;
  			if(_interactive){
  				addNodeControls();
  			}else{
  				removeNodeControls();
  			}
  			
  		}
  		
  		public function get isActive():Boolean{
  			return _isActive;
  		}
  		
  		private function visualize(data:Data):void
		{
			_vis = initDataProps( data )
			_vis.addEventListener(VisualizationEvent.UPDATE, onUpdate);
			addLayout();
			addLabels();
			_vis.update();
			_vis.data.nodes.visit(labelVisitor);// give labels backgrounds for better hitareeas
			rawChildren.addChild(_vis);
			if (_bounds) resize(_bounds);
		}
		
		private function initDataProps( data:Data ):Visualization{
			// place shorter names at the end of the data list
			// that way they will the easiest to mouse over later
			data.nodes.sortBy("-data.name.length");
			
			// prepare data with default settings
			data.nodes.setProperties({
				shape: null,                  // no shape, use labels instead
				visible: eq("childDegree",0) // only show leaf nodes
			
			});
			data.edges.setProperties({
				lineWidth: defaultEdgeThickness,
				lineColor: 0xff0055cc,
				arrowHeight:15,
				arrowWidth: 10,
				alpha:defaultEdgeAlpha,
				mouseEnabled: false ,          // non-interactive edges
				visible: true
			});
			
			
			return  new Visualization(data);
		}
		
		private function addLabels():void{
			var labeler:RadialLabeler = new RadialLabeler("data.name", true, _fmt, eq("childDegree",0))  // leaf nodes only
			labeler.textMode = TextSprite.EMBED;
			_vis.operators.add(labeler);
			
		}
		
		
		
		private function addLayout():void{
			var layout:CircleLayout = new CircleLayout("depth", null, true),
				router:BundledEdgeRouter = new BundledEdgeRouter(.975);
				//toolTip:TooltipControl = new TooltipControl(EdgeSprite;
			layout.startRadiusFraction = .25;
			
			_vis.operators.add(layout);
			_vis.operators.add(router);
		}
		
		private function addNodeControls():void{
			if(!_nodeHoverControls) _nodeHoverControls = new HoverControl( NodeSprite, HoverControl.DONT_MOVE, onNodeRollover, onNodeRollout );
			if(!_nodeClickControls) _nodeClickControls = new ClickControl( NodeSprite,	1, onNodeClick );	
			_vis.controls.add(_nodeHoverControls);
			_vis.controls.add(_nodeClickControls);			
			_vis.data.nodes.visit(enableNodesVistor);		
		}
		
		
		private function removeNodeControls():void{
			_vis.controls.remove(_nodeHoverControls);
			_vis.controls.remove(_nodeClickControls);
			_vis.data.nodes.visit(disableNodesVistor)
		}
		
		private function enableNodesVistor(node:NodeSprite):Boolean{
			if(node.childDegree != 0)return false;
			node.buttonMode = true;
			node.props.label.color = 0
			return false;
		}
		private function enableEdgeVistor(edge:EdgeSprite):Boolean{
			edge.mouseEnabled = true;
			edge.buttonMode = true;
			return false;
		}
		private function disableEdgeVistor(edge:EdgeSprite):Boolean{
			edge.mouseEnabled = false;
			edge.buttonMode = false;
			return false;
		}
		private function disableNodesVistor(node:NodeSprite):Boolean{
			if(node.childDegree != 0)return false;
			node.buttonMode = false;
			node.props.label.color = 0xcccccc;
			return false;
		}
		private function labelVisitor(node:NodeSprite):Boolean{
			var tf:TextField;
			if(node.childDegree != 0 ) return false;
			tf = TextSprite(node.props.label).textField
			tf.background = true;
			return false
		}
				
		private function addEdgeControls():void{
			if(!_edgeHoverControls) _edgeHoverControls = new HoverControl( EdgeSprite, HoverControl.DONT_MOVE, onEdgeRollover, onEdgeRollout );
			if(!_edgeClickControls) _edgeClickControls = new ClickControl( EdgeSprite,	1, onEdgeClick );	
			_vis.controls.add(_edgeHoverControls);
			_vis.controls.add(_edgeClickControls);
			selectedNode.visitEdges(enableEdgeVistor, NodeSprite.OUT_LINKS);	
		}
		
		private function removeEdgeControls():void{
			_vis.controls.remove(_edgeHoverControls);
			_vis.controls.remove(_edgeClickControls);
			selectedNode.visitEdges(disableEdgeVistor, NodeSprite.OUT_LINKS);
		}
		
		private function onNodeClick(evt:SelectionEvent):void{
			
			if(evt.ctrlKey)unhighlight(evt, NodeSprite.IN_LINKS);
			removeKeyboardEvents();
			if(!selectedNode || evt.node != selectedNode){
				selectedNode = evt.node;
			}else{
				selectedNode = null;
			}
			
			dispatchEvent( evt );
		}
		
		private function onEdgeClick(evt:SelectionEvent):void{
			dispatchEvent( evt );
		}
			
		
		private function addKeyboardEvents():void{
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
			stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
		}
		private function removeKeyboardEvents():void{
			stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
			stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
		}
		
		
		
		/** Add highlight to a node and connected edges/nodes */
		private function onNodeRollover(evt:SelectionEvent):void{
			_focusNode= evt.node;
			if(evt.ctrlKey){
				hiLightEdges( _focusNode, NodeSprite.IN_LINKS, inEdgeColour );
			}else{
				hiLightEdges( _focusNode, NodeSprite.OUT_LINKS, outEdgeColour );
			}

			addKeyboardEvents();

			// highlight mouse-over node
			_focusNode.props.label.color = 0x990000;
			
			dispatchEvent(evt);
				
			
		}
		
		private function onNodeRollout(evt:SelectionEvent):void{
			if(selectedNode)return
			removeKeyboardEvents();
			_focusNode = null;
			if(selectedNode != evt.node)unhighlight(evt);
			dispatchEvent(evt);
		}
		
		
		private function onEdgeRollover(evt:SelectionEvent):void{
			if(evt.edge.source != selectedNode)return
			_focusEdge= evt.edge;
			_focusEdge.lineColor = rolloverEdgeColour;
			//_focusEdge.source.props.label.color = selectedEdgeColour;
			//_focusEdge.target.props.label.color = selectedEdgeColour;
			
			dispatchEvent(evt);
			forceRedraw();
			
		}
		
		private function onEdgeRollout(evt:SelectionEvent):void{
			if(evt.edge.source != selectedNode)return
			_focusEdge.lineColor = selectedEdgeColour;
			//_focusEdge.source.props.label.color = 0xff000000;
			//_focusEdge.target.props.label.color = 0xff000000;
			_focusEdge= null;
			dispatchEvent(evt);
			forceRedraw();
		}
		
		private function forceRedraw():void{
			DirtySprite.renderDirty();
		}
		
		
		private function onKeyDown(event:KeyboardEvent):void{
			 if (event.keyCode == Keyboard.CONTROL) 
				hiLightEdges( _focusNode, NodeSprite.IN_LINKS, inEdgeColour );
		}
		
		private function onKeyUp(event:KeyboardEvent):void{
			if (event.keyCode == Keyboard.CONTROL) 
				hiLightEdges( _focusNode, NodeSprite.OUT_LINKS, outEdgeColour );
		}
		
		private function hiLightEdges( node:NodeSprite, edgetype:uint, colour:uint ):void{
			
			// apply slection properties
			node.visitEdges(function(e:EdgeSprite):void {
				e.alpha = selectedEdgeAlpha;
				e.lineColor = colour;
				if(edgetype == NodeSprite.IN_LINKS)e.source.props.label.color = colour;
				if(edgetype == NodeSprite.OUT_LINKS)e.target.props.label.color = colour;
				e.lineWidth = selectedEdgeThickness;
				e.arrowType = ArrowType.TRIANGLE,
				_vis.marks.setChildIndex(e, _vis.marks.numChildren-1);
			}, edgetype);
			
			unhighlight( node, (edgetype == NodeSprite.IN_LINKS)?NodeSprite.OUT_LINKS:NodeSprite.IN_LINKS )
		}
		
		private function deselectNode(n:*, edgetype:uint=3):void
		{
			var node:NodeSprite = n is NodeSprite ?
				NodeSprite(n) : SelectionEvent(n).node;
			
			removeEdgeControls();
			addNodeControls();
			// set everything back to normal
			node.props.label.color = 0;
			node.setEdgeProperties({
				alpha: defaultEdgeAlpha,
				lineColor: defautEdgeColour,
				lineWidth:defaultEdgeThickness,
				arrowType:ArrowType.NONE,
				"source.props.label.color": 0,
				"target.props.label.color": 0
			}, edgetype);
		}

		
		/** Remove highlight from a node and connected edges/nodes */
		private function unhighlight(n:*, edgetype:uint=3):void
		{
			var node:NodeSprite = n is NodeSprite ?
				NodeSprite(n) : SelectionEvent(n).node;
			
			
			// set everything back to normal
			node.props.label.color = 0;
			node.setEdgeProperties({
				alpha: defaultEdgeAlpha,
				lineColor: defautEdgeColour,
				
				lineWidth:defaultEdgeThickness,
				arrowType:ArrowType.NONE,
				"source.props.label.color": 0,
				"target.props.label.color": 0
			}, edgetype);
		}
		
		private function selectNode(n:*, edgetype:uint=3):void
		{
			var node:NodeSprite = n is NodeSprite ?
				NodeSprite(n) : SelectionEvent(n).node;

			removeNodeControls();
			addEdgeControls();
			
			node.props.label.color = 0;
			
			node.setEdgeProperties({
				alpha: selectedEdgeAlpha,
				lineColor: selectedEdgeColour,				
				lineWidth:selectedEdgeThickness,
				arrowType:ArrowType.TRIANGLE,
				"source.props.label.color": 0,
				"target.props.label.color": selectedNodeColour
			}, edgetype);
		}
		
			
		
		
		public function resize(bounds:Rectangle):void
		{
			_bounds = bounds;
			
			if (_vis) {
				// automatically size labels based on bounds
				var d:Number = Math.min(_bounds.width, _bounds.height);
				_vis.data.nodes.setProperty("props.label.size",
					(d <= 650 ? 10 : d <= 725 ? 11 : 12),
					null, eq("childDegree",0));
				
				// compute the visualization bounds
				_vis.bounds.x = _bounds.x;
				_vis.bounds.y = _bounds.y //+ (0.06 * _bounds.height);
				_vis.bounds.width = _bounds.width;
				_vis.bounds.height = _bounds.height //- (0.05 * _bounds.height);
				// update
				_vis.update();
				
				// layout legend and details
				
			
				// forcibly render to eliminate partial update bug, as
				// the standard RENDER event routing can get delayed.
				// remove this line for faster but unsynchronized resizes
				DirtySprite.renderDirty();
			}
		}
		
		// --------------------------------------------------------------------
		
	
		
	} // end of class DependencyGraph
}